/******************************************************************************
 * SAGE - Scalable Adaptive Graphics Environment
 *
 * Module: gpuCompression.cpp
 * Author : Byungil Jeong
 *
 * Copyright (C) 2004 Electronic Visualization Laboratory,
 * University of Illinois at Chicago
 *
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following disclaimer
 *    in the documentation and/or other materials provided with the distribution.
 *  * Neither the name of the University of Illinois at Chicago nor
 *    the names of its contributors may be used to endorse or promote
 *    products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Direct questions, comments etc about SAGE to bijeong@evl.uic.edu or 
 * http://www.evl.uic.edu/cavern/forum/
 *
 *****************************************************************************/

#if defined(SAGE_GPU)

#include "gpuCompression.h"

const GLenum fboTexture[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT,
                           GL_COLOR_ATTACHMENT2_EXT, GL_COLOR_ATTACHMENT3_EXT }; 
const GLenum textureConst[] = { GL_TEXTURE0, GL_TEXTURE1, GL_TEXTURE2, GL_TEXTURE3 };                         
const char* shaderNames[] = {"blockGeneration", "RLECompare", "RLEinit", "RLEmerge"};

gpuCompression::gpuCompression(sailConfig config, GLuint input) 
   : width(config.resX), height(config.resY), blockWidth(config.blockX), blockHeight(config.blockY)
{   
   numCols = (float)ceil((float)width/blockWidth);
   numRows = (float)ceil((float)height/blockHeight);
   inputTex = input;
   
   glGenFramebuffersEXT(2, frameBuffers);
   
   int numTexture = GPU_TEXTURE_NUM;
   int compNum = 4;
   int attachNum = 2;
   bool varyingFormat = false;
   GLenum pixelFormat = GL_RGBA;
   
   switch (config.compression) {
      case RLE_COMP :
         numTexture = 4;
         compNum = 3;
         attachNum = 2;
         pixelFormat = GL_RGB;
         break;
      case DXT_COMP :
         
         break;
      case LUV_COMP :
         varyingFormat = true;
         break;
      default:   
         numTexture = 4;
         compNum = 3;
         attachNum = 2;
         pixelFormat = GL_RGB;
         break;
   }

   if (!varyingFormat) {   
      glGenTextures(numTexture, textures);

      for (int i=0; i<numTexture; i++) {
         glBindTexture(GL_TEXTURE_2D, textures[i]);
         glTexImage2D(GL_TEXTURE_2D, 0, compNum, width, height, 0, pixelFormat, GL_UNSIGNED_BYTE, 0);
         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
      }

      for (int i=0; i<2; i++) {
         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frameBuffers[i]);
         for (int j=0; j<attachNum; j++) 
            glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, fboTexture[j], GL_TEXTURE_2D, textures[i*attachNum+j], 0);
      }
   }
      
   glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );   

   GLchar *FragmentShaderSource;
   for (int i=0; i<GPU_SHADER_NUM; i++) {
      readShaderSource((char *)shaderNames[i], NULL, &FragmentShaderSource);
      // get shader program handle
      shaderProg[i] = installShaders(NULL, FragmentShaderSource);
   
      if (!shaderProg[i]) {
         std::cout << "gpuCompression : error in reading shader source" << std::endl;
         exit(0);
      }   
   }           
}

int gpuCompression::RLEcompress(void *buf)
{
   glPushAttrib(GL_ALL_ATTRIB_BITS);
   glDisable(GL_DEPTH_TEST);
   
   int fboID = 0;
   
   glMatrixMode(GL_PROJECTION);
   glPushMatrix();
   glLoadIdentity();
   gluOrtho2D(-1, 1, -1, 1);
   glMatrixMode(GL_MODELVIEW);
   glPushMatrix();
   glLoadIdentity();
   glViewport(0, 0, width, height);
   
   glUseProgram(shaderProg[1]);

   glUniform2f(getUniLoc(shaderProg[1], "winSize"), width, height);
   glUniform2f(getUniLoc(shaderProg[1], "blockSize"), blockWidth, blockHeight);
   glUniform2f(getUniLoc(shaderProg[1], "blockDim"), numCols, numRows);
   glUniform1f(getUniLoc(shaderProg[1], "blockPixels"), blockWidth*blockHeight);

   glActiveTexture(GL_TEXTURE0);
   glBindTexture(GL_TEXTURE_2D, inputTex);
   glUniform1i(getUniLoc(shaderProg[1], "colorMap"), 0);
   
   glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, frameBuffers[fboID] );
   glDrawBuffers(2, fboTexture);
   glClear(GL_COLOR_BUFFER_BIT);
   
   glBegin(GL_QUADS); 
      glTexCoord2f(0, 0); glVertex3f(-1, -1, -0.5);
      glTexCoord2f(1, 0); glVertex3f( 1, -1, -0.5);
      glTexCoord2f(1, 1); glVertex3f( 1,  1, -0.5);
      glTexCoord2f(0, 1); glVertex3f(-1,  1, -0.5);
   glEnd();

   //glReadBuffer(fboTexture[0]);
   //glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)buf);   
   
   glUseProgram(shaderProg[2]);
   glUniform2f(getUniLoc(shaderProg[2], "winSize"), width, height);

   glBindTexture(GL_TEXTURE_2D, textures[fboID*2+1]);
   glUniform1i(getUniLoc(shaderProg[2], "colorMap"), 0);
   fboID = 1 - fboID;
   glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, frameBuffers[fboID] );
   glDrawBuffer(fboTexture[0]);
   glClear(GL_COLOR_BUFFER_BIT);
   
   glBegin(GL_QUADS); 
      glTexCoord2f(0, 0); glVertex3f(-1, -1, -0.5);
      glTexCoord2f(1, 0); glVertex3f( 1, -1, -0.5);
      glTexCoord2f(1, 1); glVertex3f( 1,  1, -0.5);
      glTexCoord2f(0, 1); glVertex3f(-1,  1, -0.5);
   glEnd();
   
   glUseProgram(shaderProg[3]);
   glUniform2f(getUniLoc(shaderProg[3], "winSize"), width, height);

   for (int len = 8; len<=blockWidth; len = len*2) {
      glBindTexture(GL_TEXTURE_2D, textures[fboID*2]);
      glUniform1i(getUniLoc(shaderProg[3], "colorMap"), 0);
      glUniform1f(getUniLoc(shaderProg[3], "seqLength"), len);
      
      fboID = 1 - fboID;
      glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, frameBuffers[fboID] );
      glDrawBuffer(fboTexture[0]);
      glClear(GL_COLOR_BUFFER_BIT);

      glBegin(GL_QUADS); 
         glTexCoord2f(0, 0); glVertex3f(-1, -1, -0.5);
         glTexCoord2f(1, 0); glVertex3f( 1, -1, -0.5);
         glTexCoord2f(1, 1); glVertex3f( 1,  1, -0.5);
         glTexCoord2f(0, 1); glVertex3f(-1,  1, -0.5);
      glEnd();
   }

   glReadBuffer(fboTexture[0]);
   glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)buf);   
   
   char *info = (char *)buf;
   glReadBuffer(fboTexture[0]);
   glReadPixels(0, 0, width, height, GL_BLUE, GL_UNSIGNED_BYTE, (GLvoid *)&info[width*height*3]);   
   
   // !!! check info[] value here !!! //
   
   glPopMatrix();
   glMatrixMode(GL_PROJECTION);
   glPopMatrix();
   glMatrixMode(GL_MODELVIEW);
   
   glPopAttrib();
   glUseProgram(0);
   
   return blockWidth*blockHeight*3;
}

int gpuCompression::blockGeneration(void *buf)
{
   glPushAttrib(GL_ALL_ATTRIB_BITS);
   glDisable(GL_DEPTH_TEST);
   
   int fboID = 0;
   
   glMatrixMode(GL_PROJECTION);
   glPushMatrix();
   glLoadIdentity();
   gluOrtho2D(-1, 1, -1, 1);
   glMatrixMode(GL_MODELVIEW);
   glPushMatrix();
   glLoadIdentity();
   glViewport(0, 0, width, height);
   
   glUseProgram(shaderProg[0]);

   glUniform2f(getUniLoc(shaderProg[0], "winSize"), width, height);
   glUniform2f(getUniLoc(shaderProg[0], "blockSize"), blockWidth, blockHeight);
   glUniform2f(getUniLoc(shaderProg[0], "blockDim"), numCols, numRows);
   glUniform1f(getUniLoc(shaderProg[0], "blockPixels"), blockWidth*blockHeight);

   glActiveTexture(GL_TEXTURE0);           
   glBindTexture(GL_TEXTURE_2D, inputTex);
   glUniform1i(getUniLoc(shaderProg[0], "colorMap"), 0);
   
   glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, frameBuffers[fboID] );
   glClear(GL_COLOR_BUFFER_BIT);
   
   glBegin(GL_QUADS); 
      glTexCoord2f(0, 0); glVertex3f(-1, -1, -0.5);
      glTexCoord2f(1, 0); glVertex3f( 1, -1, -0.5);
      glTexCoord2f(1, 1); glVertex3f( 1,  1, -0.5);
      glTexCoord2f(0, 1); glVertex3f(-1,  1, -0.5);
   glEnd();

   glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)buf);   

   glPopMatrix();
   glMatrixMode(GL_PROJECTION);
   glPopMatrix();
   glMatrixMode(GL_MODELVIEW);
   
   glPopAttrib();
   glUseProgram(0);
   
   return blockWidth*blockHeight*3;
}

#endif
